'''


    DataK functions and objects.


'''

import glob
import os.path
import time
import re
import pprint

_DATASERVER_ATTRIB = "DataKServer"
ITEMKEY = "__ITEM__"

class DataKError( Exception ):
    pass

class ConnectionError( DataKError ):
    def __init__( self, connection,  msg=None ):
        if msg is None:
            msg = "Connection failed."
        DataKError.__init__( self, repr(connection)+': '+msg )

class IdError( DataKError ):
    def __init__( self, connection, id ):
        DataKError.__init__(
            self,
            "Id "+repr(id)+" doesn't exist or is not allowed by connection "+str(connection)
        )

class InsertError( DataKError ):
    def __init__( self, path, umsg=None, exception=None ):
        msg = "Error while creating item in "+repr(path)
        if umsg is not None:
            msg += ', '+umsg
        if exception is not None:
            msg += ": "+str(exception)
        DataKError.__init__( self, msg )

class InsertExistsError( InsertError ):
    def __init__( self, path ):
        InsertError.__init__(
            self,
            "Item in "+repr(path)+" allready exist !"
        )

class GetError( DataKError ):
    def __init__( self, item, path ):
        DataKError.__init__(
            self,
            "Invalid path "+repr(path)+" under "+str(item)
        )

class FormulaError( DataKError ):
    def __init__( self, item, attrName, formula, msg ):
        DataKError.__init__(
            "In "+repr(item)+" the formula of attribute "+repr(attrName)
            +" ("+repr(formula)+") is invalid:\n"+msg
        )

class ExecError( DataKError ):
    def __init__( self, item, query,  code,  exception=None ):
        msg = "Exec( "+repr(query)+" ) reduced to "+repr(code)+" in "+repr(item)
        if exception is not None:
            msg += ": "+str(exception)
        DataKError.__init__( self, msg )

class Item( object ):
    '''
    The item is the basic data of DataK. see help(DataK) for more informations.
    '''
    _skipInitCheck = False # use to speed up internal Item creation
    _classAttribs = ( # real not protected attribs, used to distinguish from magic attributes
        'id', 'name', 'value',
        'CanGet', 'Get',  'GetParent', 'GetChildren', 'GetItems',
        'Insert', 'SortBy', 'GetFormula',  'Select',
    )
    # Regexp validating characters in an Item name before creation:
    _nameValidator = re.compile(r'''^\w+$''')
    # Regexp matching a path (see Get() and Exec())
    _pathFinder = re.compile(
        re.sub(
            '(\s+|#[^#]+#)', '',
            '''
            (  # a path then attr or just a path #
                (                            # the path #
                    \.{0,2}                     # starts by . or .. or nothing #
                    (
                        /                           # then a / #
                        (
                            [\w\?\*]*           # then a wildcard #
                            |                       # or #
                            \.{1,2}             # . or .. #
                        )
                    )+                            # the path has var len #
                )+
                (
                    \.[\w\?\*]+\~?           # the attribute with its '.' #
                )?                                 # is optional #
                |                           # or #
                (
                    \.[\w\?\*]+\~?   # just an attribute #
                )
            )
            '''
        )
    )

    # This one is not a property because the Connection needs to override it.
    # So I use my __getattribute__ to act like a readOnly property named 'id'
    def _GetId(self):
        '''Get the id of the Item.'''
        return self._id
    #id = property( _GetId, "The id of the Item." )


    def _GetName(self):
        '''Get the name of the Item.'''
        return os.path.basename( self._id )
    name = property( _GetName, "The name of the Item." )

    def _GetValue(self):
        '''Get the value of the Item.'''
        v = self._GetAttr('_value')
        if v is None:
            v = self.name
        return v
    def _SetValue(self, value):
        return self._SetAttr( '_value', value )
    value = property( _GetValue, _SetValue, "The value of the Item." )

    def _GetPath(self):
        '''Get the absolute path of the Item.'''
        return self._cnt.GetItemPath( self )
    _path = property( _GetPath, "The absolute path of the Item." )

    def __init__(self, connection, id):
        '''
        Create an item for the given id.
        If the given id doesn't exist or is not accepted by the connection, an error is raised.
        '''
        object.__init__(self)
        if not self._skipInitCheck and not connection.IsItem(id):
            raise IdError( connection, id )
        self._cnt = connection
        self._id = os.path.normpath( id )
        self._attrNames = None
        self._sortBy = None

    def __repr__(self):
        return "DataK.Item("+repr(self._cnt)+','+repr(self._id)+')'

    def __str__(self):
        return self.id

    def __getattr__( self, name ):
        '''
        Will return the value for id, name, value and any existing Item's Attribute:
            name = item.name
            value = item.value
            myAttrVal = item.myAttrName
        '''
        if name == 'id':
            return self._GetId()
        if name[0] == '_' or name in self._classAttribs:
            return self.__getattribute__(name)
        return self._GetAttr( name )

    def __setattr__( self, name, value ):
        '''
        Will set the formula of the item's attribute, creating it if needed.
            item.value = 17
            item.squared = "self.value * self.value"
        '''
        if name[0] == '_' or name in self._classAttribs:
            return object.__setattr__( self, name, value )
        self._SetAttr( name, value )

    def __iter__( self ):
        '''
        Iterate thru children items:
        for ci in item:
            print ci.name, ci.value

        See also SortBy ().
        '''
        ls = [ os.path.join(self.id, f) for f in glob.glob1( self._path, '*' ) ]
        Item._skipInitCheck = True
        items = [ Item(self._cnt,f) for f in ls if self._cnt.IsItem(f) ]
        Item._skipInitCheck = False
        items = self._ApplySort( items,  self._sortBy,  False )
        return iter( items )

    def _ReadAttrNames(self):
        '''
        Read the names of the item's attributes from disk (only if needed).
        '''
        if self._attrNames is not None:
            return
        ls = glob.glob1( self._path, '*' )
        self._attrNames = [ f for f in ls if os.path.isfile( os.path.join(self._path,f) ) ]
        self._attrNames.sort()

    def _GetAttr( self, name, asFormula=False ):
        '''
        Returns the value of the item's Attribute 'name'.
        If name ends with '?' the Attribute value is searched on the Item path
        '''
        self._ReadAttrNames()

        if name.endswith('~'):
            if name[:-1] in self._attrNames:
                print "   h a",  self
                return self._GetAttr(name[:-1])
            if self.id: # i'm not root
                return self.GetParent()._GetAttr(name)
            return None

        if name not in self._attrNames:
            return None

        r = open( os.path.join(self._path,name), 'r' )
        content = r.read()
        r.close()

        if asFormula:
            return content

        if not content.strip():
            return ''

        #print '########### REDUCING', content
        exec( "v="+content )
        return v

    def _SetAttr( self, name, formula ):
        '''
        Set the formula of the item's attribute 'name' to value, creating the
        attribute if needed.

        It is an error to use a reference to the attribute itself in its
        formula:
            item.myAttr = "item.myAttr+2" will raise FormulaError

        '''
        content = pprint.pformat( formula )
        if isinstance( formula, basestring ):
            content = content[1:-1]
        if 'self.'+name in content:
            raise FormulaError(
                self, name, content, "Formula cannot reference its attribute!"
            )
        w = open( os.path.join(self._path,name), 'w' )
        w.write( content )
        w.close()

        self._ReadAttrNames()
        if name not in self._attrNames:
            self._attrNames.append(name)
            self._attrNames.sort()

    @classmethod
    def CanGet( cls, string ):
        '''
        Return True if string look like a path to an Item value or an Attribute value.
        See Get() for syntaxe exemples.
        '''
        match = cls._pathFinder.match(string)
        return match is not None and match.group() == string

    def Get( self, path,  sortBy=None ):
        '''
        Return the list of values pointed by path.

        Accepted syntaxes are:
          '.myAttr'                                 -> [self._item.myAttr]
          '.attr*'                                      -> [values for all attributes starting with 'attr']
          './PATH/TO/ANOTHER/ITEM'          -> [self._item/PATH/TO/ANOTHER/ITEM.value]
          '/ABS/PATH/TO/ANOTHER/ITEM'   -> [self._item._cnt/ABS/PATH/TO/ANOTHER/ITEM.value]
          '../SIBBLING/ITEM.myAttr'         -> [self._item/../SIBBLING/ITEM.myAttr]
          './PATH/CAN/WILCARD/*.myAttr' -> [values of myAttr for each child item of WILDCARD]

        If path points to an un-exting Attribute, None is returned.
        If path points to an un-exting Item, an error is raised.
        Still, path using wildcards can reach nothing without raising error. In this
        case [] is returned.

        If sortBy is given, it is used to sort the Items found by the wildcards in path.
        You can use sortBy=() to force default sorting: by value then by name.

        '''
        itemPath, attr = os.path.splitext( path )
        attr = attr[1:] # get ride of the '.'
        if not itemPath:
            return [self._GetAttr( attr )]
        items = self.GetItems( itemPath,  sortBy )
        if attr:
            return [ i._GetAttr( attr ) for i in items ]
        return [ i.value for i in items ]

    def GetItems(self, path,  sortBy=None):
        '''
        Return the items represented by path under self.
        If the path is absolute, it is be relative to the connection path.

        The path syntax must be like:
            "PATH/FROM/SELF/TO/TARGET"
            "./PATH/FROM/SELF/TO/TARGET"
            "../RELATIVE/PATH/TO/TARGET"
            "/ABS/PATH/FROM/CONNECTION/TO/TARGET"

        The path can use wildcards. If it doesn't and the item is not found, an error
        is raised.
        If sortBy is given, it is used to sort the Items found by the wildcards in paths.
        You can use sortBy=() to force default sorting: by value then by name.

        '''
        if path[0] == os.path.sep:
            # abs path requested
            return self._cnt.GetItems( path[1:] )

        pl = path.split( os.path.sep )
        childrenWildcard = pl.pop(0)
        while childrenWildcard == '.':
            if not pl:
                # no more path ?!? not valid...
                raise GetError(self,path)
            childrenWildcard = pl.pop(0)

        if pl:
            nextPath = os.path.join(*pl)
        else:
            nextPath = ''

        if childrenWildcard == '..':
            matchingChildren = ( self.GetParent(),  )
        else:
            matchingChildren = self.GetChildren( childrenWildcard,  sortBy )

        ret = []
        if nextPath:
            [ ret.extend( c.GetItems(nextPath) ) for c in matchingChildren ]
        else:
            ret.extend( matchingChildren )
        return ret

    def GetParent(self):
        '''
        Returns an Item representing the parent Item of this Item.
        Raises an error if this Item is a server root.
        '''
        parentId = os.path.dirname(self.id)
        if not parentId:
            return self._cnt
        return Item( self._cnt,  parentId )

    def GetChildren( self,  wildcard,  sortBy=None ):
        '''
        Returns a list of children with name matching wildcard.
        (Name!, not value...)

        If sortBy is given, it is used to sort the children.
        You can use sortBy=() to force default sorting: by value then by name.

        '''
        if os.path.sep in wildcard:
            raise ValueError(
                "Path not allowed:"+repr(wildcard)+". Use GetItems if you need path access."
            )

        if  glob.magic_check.search( wildcard ) is None:
            return [ Item( self._cnt,  os.path.join( self.id, wildcard ) ) ]

        ls = [ os.path.join(self.id, f) for f in glob.glob1( self._path, wildcard ) ]
        items = [ Item( self._cnt,  f ) for f in ls if self._cnt.IsItem( f ) ]
        if sortBy is None:
            sortBy = self._sortBy
        return self._ApplySort( items,  sortBy,  True )

    def GetFormula(self, attrName=None):
        '''
        Returns the formula of the given Attribute.
        Returns None if the Attribute doesn't exist.
        Returns the Item's formula if attrName is None.
        '''
        if attrName is None:
            return self._GetAttr( '_value', asFormula=True )
        return self._GetAttr( attrName, asFormula=True )

    def Insert(self, name,  linkTo=None):
        '''
        Insert a new item in this item's children list.
        Return the new item.
        Raises if the item already exists.

        If linkTo is not None, it must be the id of an valid Item. The created Item
        will be a link to this id.
        If linkTo is not None, name can be empty or None. In this case the name of
        linkTo will be used.

        Returns the new Item, with this Item's SortBy set.

        '''
        if linkTo is None:
            # CREATE AN ITEM
            path = os.path.join(self._path, name)
            if self._nameValidator.match( name ) is None:
                raise InsertError( path,  "Invalid name "+repr(name) )
            if os.path.exists( path ):
                raise InsertExistsError( path )
            try:
                os.mkdir( path )
            except Exception, err:
                raise InsertError( path, "when creating folder",  exception=err )

        else:
            # CREATE A LINK
            target = Item( self._cnt, linkTo ) # checks validity
            if not name:
                name = target.name
            elif self._nameValidator.match( name ) is None:
                raise InsertError( path,  "Invalid name "+repr(name) )
            path = os.path.join(self._path, name)
            if os.path.exists( path ):
                raise InsertExistsError( path )
            try:
                os.symlink( self._cnt.GetItemPath(target),  path )
                print "--------------------------",  linkTo,  path
            except Exception, err:
                raise InsertError( path, "when creating link to "+repr(linkTo),  exception=err )

        item = Item( self._cnt, os.path.join(self.id, name) )
        item.SortBy( self._sortBy )
        return item

    def Exec(self,  query,  sortBy=None):
        '''
        Returns the result of the given query.

        A query is a string containing python code and paths (paths are the same as input for Get()).
        The paths are resolved to values before executing the code. If the paths uses wilcards, it is resolved
        to a list. If not, it must point to an existing Item or Attribute and will be resolved as a
        single value (unlike Get())
        The value generated by the resolved code is returned.
        Example:
            Exec( ".enabled" ) -> the Item's Attribute 'enabled' has a True value
            Exec(
                ".enabled and .type == 'Task' and ./USER.login == os.environ['USER']"
            ) -> the Item is an enabled Task assigned to the current user.
            Exec( "len( ./* ) > 2 ) -> Returns True if the item has more than 2 children.

        If sortBy is given, it is used to sort the Items found by the wildcards in paths.
        You can use sortBy=() to force default sorting: by value then by name.
        '''
        def Reduce( match ):
            path = match.group()
            value = self.Get( path,  sortBy )
            if glob.magic_check.search( path ) is None:
                value = value[0]
            return repr(value)
        code = self._pathFinder.sub( Reduce,  query )

        try:
            return self._Exec( code )
        except Exception,  err:
            raise ExecError( self,  query,  code,  err)

    def _Exec( self,  code ):
        '''
        Execute the code built by Exec() and run the generated value.
        '''
        exec( 'ret = '+code )
        return ret

    def SortBy( self,  attrNames=None ):
        '''
        Affect the order of further iterations on this item like:
            for child in item:
                print child.name, child.value
        and the default behavior of method with a sortBy argument like:
            item.GetChildren( '*' )
            item.GetItems( './*' )
            item.Get( './*.myAttr' )
            item.Exec( " ','.join( ./* ) " )

        attrNames must be a list of string or None to use the default sorting.
        The default sorting is value then name.
        '''
        if not hasattr( attrNames, '__iter__' ) and attrNames is not None:
            raise ValueError( "attrName must be None or iterable (not "+repr(attrNames)+")" )
        self._sortBy = attrNames

    @staticmethod
    def _ApplySort( items,  sortBy=None,  affectItems=True ):
        '''
        Reorder the items list accordingly to sortBy.
        If sortBy id None, the default order is used: by value then by name.
        If affectItems is True, every item will be set up for the same sort in further
        iterations.
        '''
        if sortBy is None:
            sortBy = ()
        sortables = []
        for item in items:
            affectItems and item.SortBy( sortBy ) # affect only if requested
            sortable = [ item._GetAttr(attrName) for attrName in sortBy ]
            sortable.append( item.value )
            sortable.append( item.name )
            sortable.append( item )
            sortables.append( sortable )
        sortables.sort()
        return [ sortable[-1] for sortable in sortables ]

    def Select( self, what, where,  sortBy=None,  groupBy=None ):
        '''
        Returns a list like requested by what and where.
        what gives the list of Items and Attributes you want.
        where is a query executed on the matching Items. If it evaluates to False, the item
        is skipped.

        If groupBy is None:
            The returned list will contain a row for each item matching what and where,
            and will be sorted by the Attributes specified in sortBy (or by the default sort if sortBy
            is None, see SortBy(), use sortBy=() to sort by value then by name)

            The items of the returned list are dicts who's' keys will contain ITEMKEY, 'id', 'name', 'value'
            and all fields requested by what.

            example:
                (
                    {ITEMKEY:<Item>,'id':'/testA','name':'test','value':'test','myAttr':0},
                    {ITEMKEY:<Item>,'id':'/testB','name':'test','value':'test','myAttr':1},
                    {ITEMKEY:<Item>,'id':'/testC','name':'test','value':'test','myAttr':1},
                    {ITEMKEY:<Item>,'id':'/testD','name':'test','value':'test','myAttr':2},
                )

        If groupBy is an Attribute name:
            The returned list will contain list with the groupBy Attribute value at index 0
            and a list of dicts like the return value without groupBy.

            example:
                (
                    (
                        0,
                        (
                            {ITEMKEY:<Item>,'id':'/testA','name':'test','value':'test','myAttr':0},
                        ),
                    ),
                    (
                        1,
                        (
                            {ITEMKEY:<Item>,'id':'/testB','name':'test','value':'test','myAttr':1},
                            {ITEMKEY:<Item>,'id':'/testC','name':'test','value':'test','myAttr':1},
                        ),
                    ),
                    (
                        2,
                        (
                            {ITEMKEY:<Item>,'id':'/testD','name':'test','value':'test','myAttr':2},
                        ),
                    ),
                )
        ( ITEMKEY is a name defined in this scope )

        There is three possible ways to specify what and where. In any case, both arguments
        must have the same type.

        When selecting only one Attribute of only one Item, you can use two paths:
            item.Select( 'ep*/S*/P*/EDIT.start', where='../.HasHair' )
        what is the path to the Items's Attribute you want, and when is a query to execute
        on the Items found (See Exec()).

        When selecting only one Attribute on each Items, you must use two lists or tuples:
            item.Select(
                ('ep*/S*/P*/EDIT.start', 'ep*/S*.order' ),
                where=( '../.HasHair and not ../.disabled', '.act == 3')
            )
        Its does the same as calling several times Select with the paths arguments.

        When selecting several Attributes, you must use two dicts with the same keys:
            item.Select(
                {
                    'ep*/S*/P*/EDIT' : ('start','end'),
                    'ep*/S*'             : ('.order',),
                },
                where={
                    'ep*/S*/P*/EDIT' : '../.HasHair and not ../.disabled',
                    'ep*/S*'             : '.act == 3'
                }
            )
        what keys are the paths to the items, and what values are lists of Attributes you
        want for their key.
        where keys are the same than what keys, and where values are query to execute
        on the Items found from the key (See Exec()).
        '''
        # get two dicts from the args:
        if type(what) != type(where):
            raise ValueError(
                "Bad type for what/where: both must be of the same type "
                "(not "+str(type(what))+'/'+str(type(where))+')'
            )
        if isinstance(what,  basestring):
            path,  attr = os.path.splitext(what)
            attr = attr[1:]
            what = {path: [ a for a in (attr, ) if a.strip() ] }
            where = {path:where}
        elif not isinstance(what,  dict) and hasattr(what,  '__iter__'):
            nb = len(what)
            if len(where) != nb:
                raise ValueError("Bad value for what/where: both must be the same length")
            iterWhat = what
            iterWhere = where
            what = {}
            where = {}
            for aWhat,  aWhere in zip( iterWhat,  iterWhere ):
                path,  attr = os.path.splitext(aWhat)
                attr = attr[1:]
                if path in what:
                    raise ValueError("Bad value for what,  item is not unic: "+path)
                what[path] = [ a for a in (attr, ) if a.strip() ]
                where[path] = aWhere
        elif not isinstance(what,  dict):
            raise ValueError("Bad type for what/where: both must be dict, string or iterable.")

        # fill the table
        table = []
        for path, attr in what.iteritems():
            print ">>> selecting",  path,  'attr:',  attr
            items = self.GetItems( path )
            if not items:
                print "    nothing"
                continue
            for item in items:
                print "    >>> on",  item,  '?',  where[path]
                if item.Exec( where[path],  sortBy ):
                    table.append( item._GetSelectedDict( attr) )
                    print "       yes"
                else:
                    print "       no"

        return table

    def _GetSelectedDict( self, attributes ):
        ret = {
            ITEMKEY: self,
            'id': self.id,
            'name':self.name,
            'value':self.value,
        }
        ret.update( [ ( attr, self._GetAttr(attr) ) for attr in attributes ] )
        return ret

class Connection( Item ):
    '''
    The connection is needed to access items in a DataK database.
    '''
    # This is why Item cannot define 'id' as a property: I must override it here:
    def _GetId(self):
        '''Get the id of the Item.'''
        return ''
    #id = property( _GetId, "The id of the Item." )

    def __init__(self, path):
        '''
        Connect to the given path.
        '''
        if not os.path.isdir( path ):
            raise ConnectionError( "Path not valid for connection: "+repr(path) )
        self._connected = True
        self._id = '' # needed to init the base class with self as connection.
        self._skipInitCheck = True
        Item.__init__( self, self, path )
        self._skipInitCheck = False
        if self._GetAttr( _DATASERVER_ATTRIB ) is None:
            self._connected = False
            raise ConnectionError( self )

    def __repr__(self):
        return "DataK.Connection("+repr(self._id)+')'

    def __str__(self):
        return self._id

    def GetParent(self):
        '''
        It is an error to get he parent of the connection.
        '''
        raise IdError( self,  '<parentId>')

    def IsItem(self, id):
        '''
        Returns True if the given id represents an existing and valid item for this
        connection.
        '''
        return (
            self._connected
            and os.path.isdir( os.path.join( self._path, id) )
        )

    def GetItemPath(self, item):
        if not self._connected:
            raise ConnectionError(self,  "Not connected !")
        if item is self:
            return self._id
        return os.path.join( self._id, item._id)


def CreateDataK( path ):
    '''
    Create a new DataK server under path and return a connection on it.
    '''
    if os.path.exists( path ):
        raise InsertExistsError( path )
    try:
        os.mkdir( path )
        w = open( os.path.join( path, _DATASERVER_ATTRIB ), 'w' )
        w.write( repr("Created on "+time.ctime()+" by "+os.environ["USER"]) )
        w.close()
    except Exception, err:
        raise InsertError( path, exception=err )

    cnt = Connection( path )
    return cnt

def Connect( path ):
    '''
    Return a connection to the given DataK server
    '''
    return Connection( path )

def DeleteDataK( path ):
    '''
    Delete the DataK server under path.
    BEWARE:
    This will not invalidate your items and connections and acting on them
    will re-create files that will no longer be accessible by DataK !
    '''
    # check if this is a valid server:
    cnt = Connection( path )
    # delete it
    os.rmdir( path )

def Print( item, attrNames=(), indent=0 ):
    '''
    Prints the item and the requested attributes.
    '''
    identStr = '  '
    idt = indent*identStr
    if not indent:
        print item._id+'='+repr(item.value)
    else:
        print idt+item.name+'='+repr(item.value)

    for n in attrNames:
        v = item._GetAttr(n)
        if v is not None:
            print idt+'->'+n+'='+repr(v)

def PrintTree( item, attrNames=(), sortBy=None, ident=0 ):
    if sortBy is not None:
        item.SortBy( sortBy )
    Print( item, attrNames, ident )
    for sub in item:
        PrintTree( sub, attrNames, sortBy, ident+1 )







def __TestItemPathFinder():
    tests = (
        (True, '.attrib'),
        (True, '.attrib~'),
        (True, './.attrib'),
        (True, './.attrib~'),
        (True, '../.attrib'),
        (True, '../.attrib~'),
        (True, './PATH/TO/ITEM'),
        (False, './PATH/TO/ITEM~'),
        (True, './PATH/TO/ITEM.attrib'),
        (True, './PATH/TO/ITEM.attrib~'),
        (True, '/ABS/PATH/TO/ITEM'),
        (False, '/ABS/PATH/TO/ITEM~'),
        (True, '/ABS/PATH/TO/ITEM.attrib'),
        (True, '/ABS/PATH/TO/ITEM.attrib~'),
        (True, '../RELATIV/PATH/TO/../ITEM'),
        (False, '../RELATIV/PATH/TO/../ITEM~'),
        (True, '../RELATIV/PATH/TO/../ITEM.attrib'),
        (True, '../RELATIV/PATH/TO/../ITEM.attrib~'),
        (False,  'variableName'),
        (False,  'variableName~'),
        (False,  'os.environ["User"]'),
        (False,  ' '),
        (False,  '1/2'),
        (False,  '*'),
        (False,  '?'),
        (True, '.*'),
        (True, '.attrib_*'),
        (True, '.attrib_*~'),
        (True, '.attrib_?Ok'),
        (True, '.attrib_?Ok~'),
        (True, './PATH/TO/TRUC_*'),
        (False, './PATH/TO/TRUC_*~'),
        (True, './P???/TO/TRUC_*'),
        (True, './P???/TO/TRUC_*.order'),
        (True, './P???/TO/TRUC_*.order~'),
    )
    for i,  (result, test) in enumerate(tests):
        if Item.CanGet( test ) != result:
            raise Exception( "bad result for #"+str(i)+": "+repr(test) )
    print ' Path Finder Tests passed.'


if __name__ == '__main__':
    __TestItemPathFinder()








